package edu.gmu.middleware;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * The Header provides generic attributes required of
 * every message - mainly the topic and message length.
 * <p>
 * The endianFlag (a.k.a. starting message delimiter) is a design issue.
 * I decided to support both little and big endian byte order, however,
 * big endian is the preferred order and default.  Leaving it out
 * would have made things simpler, however, since we are using a
 * copy approach, it isn't that much extra to support it.
 * <p>
 * Note that this object is not marshalable itself although it's attributes
 * do get marshaled to the byte[].  It is the responsibility of the
 * Marshaler/Unmarshaler to do this.
 * 
 * @author  James H. Pope
 * @version $Revision:$ $Date:$
 */
public class Header //implements Message
{
    /**
     * The size in bytes of the header
     */
    public static final int SIZEOF = 12;
    
    /**
     * Size in bytes of the topic
     */
    public static final int MESSAGE_SYNC_SIZE   = 4;
    
    /**
     * Size in bytes of the messageLength
     */
    public static final int MESSAGE_LENGTH_SIZE = 4;
    
    /**
     * The length of the message including the SMD and this length.
     * This is the length in bytes.
     */
    private EndianFlag endianFlag = EndianFlag.BIG_ENDIAN;
    
    /**
     * The length of the message including the SMD and this length.
     * This is the length in bytes.
     */
    private int length = 0;
    
    /**
     * The id the message being transmitted.
     */
    private int topic = 0;
    
    /**
     * Creates a new Header with default parameters.
     */
    public Header( )
    {
        
    }
    
    /**
     * Sets the endianFlag
     * @param endianFlag
     */
    public void setEndianFlag( EndianFlag endianFlag )
    {
        this.endianFlag = endianFlag;
    }
    
    /**
     * Gets the endianFlag
     * @return endianFlag
     */
    public EndianFlag getEndianFlag( )
    {
        return this.endianFlag;
    }
    
    /**
     * Sets the length
     * @param length
     */
    public void setLength( int length )
    {
        this.length = length;
    }
    
    /**
     * Gets the length
     * @return length
     */
    public int getLength( )
    {
        return this.length;
    }
    
    /**
     * Sets the topic
     * @param topic
     */
    public void setTopic( int topic )
    {
        this.topic = topic;
    }
    
    /**
     * Gets the topic
     * @return topic
     */
    public int getTopic( )
    {
        return this.topic;
    }
    
    /**
     * Writes this objects attributes to the specified array.
     *
     * @param array to write this object to.
     * @exception ArrayIndexOutOfBoundsException if invalid input.
     */
    public void marshal(ByteBuffer out) throws IOException
    {
        // Always use big-endian because LITTLE_ENDIAN is already swapped
        out.putInt(this.endianFlag.getId());
        
        if( this.endianFlag == EndianFlag.BIG_ENDIAN )
        {
            out.order( ByteOrder.BIG_ENDIAN );
        }
        else
        {
            out.order( ByteOrder.LITTLE_ENDIAN );
        }
        out.putInt(length);
        out.putInt(topic);
    }

    /**
     * Reads this objects attributes from the specified array.
     *
     * @param array to create this object from.
     * @exception ArrayIndexOutOfBoundsException if invalid input.
     */
    public void unmarshal(ByteBuffer in) throws IOException
    {
        int flag = in.getInt();
        //System.out.println( "unmarshal flag = "+ Integer.toHexString(flag) );
        this.endianFlag = EndianFlag.get(flag);
        
        if( this.endianFlag == EndianFlag.BIG_ENDIAN )
        {
            in.order( ByteOrder.BIG_ENDIAN );
        }
        else
        {
            in.order( ByteOrder.LITTLE_ENDIAN );
        }
        this.length    = in.getInt();
        this.topic = in.getInt();
    }
    
    public String toString()
    {
        StringBuffer buf = new StringBuffer();
        if( this.endianFlag != null )
        {
            buf.append( "SYNC="+Integer.toHexString(endianFlag.getId()) );
        }
        else
        {
            buf.append( "SYNC=null" );
        }
        buf.append( " LENGTH="+this.length );
        buf.append( " TOPIC="+this.topic );
        return buf.toString();
    }
    
    
    
    /**
     * Utility debugging to get raw bytes as a hex string
     * @param bytes
     * @return
     */
    public static String toString( ByteBuffer buffer )
    {
        StringBuffer buf = new StringBuffer();
        byte[] bytes = buffer.array(); // only works on non-direct!!!
        int limit = buffer.limit();
        for (int i = 0; i < limit; i++)
        {
            byte b = bytes[i];
            String sb = Integer.toHexString(b&0xff);
            if( sb.length() == 1 ) sb = "0"+sb;
            
            buf.append( " "+sb );
        }
        return buf.toString();
    }
    
    /**
     * Utility debugging to get raw bytes as a hex string
     * @param bytes
     * @return
     */
    public static String toString( byte[] buffer )
    {
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < buffer.length; i++)
        {
            byte b = buffer[i];
            String sb = Integer.toHexString(b&0xff);
            if( sb.length() == 1 ) sb = "0"+sb;
            
            buf.append( " "+sb );
        }
        return buf.toString();
    }
}
